package com.wzw.basics.concurrent.vola;

/**
 * volatile
 * -- 只能修饰变量
 * -- 保证变量在多线程中在内存里的可见性
 * -- 无法保证变量的原子性
 *
 * @author Wangzhiwen
 */
public class VolatileDemo {

    public static void main(String[] args) {

        //visibility.atomicity();

//        OrderRearrangement.rearrange();
        Singleton.m1();
    }
}

class visibility {
    // 保证变量的可见性，多线程情况下只要有一个线程改变了变量的值，其他线程就能立马感知
    public volatile int number;

    /**
     * volatile 多线程情况下无法保证变量的原子性
     */
    public void numberPlus() {
        number++;
    }

    /**
     * 无法保证原子性
     * 假设我们没有加 synchronized
     * 那么第一步就可能存在着，三个线程同时通过getfield命令，拿到主存中的 n值，
     * 然后三个线程，各自在自己的工作内存中进行加1操作，但他们并发进行 iadd 命令的时候，因为只能一个进行写，所以其它操作会被挂起
     * 假设1线程，先进行了写操作，在写完后，volatile的可见性，应该需要告诉其它两个线程，主内存的值已经被修改了，
     * 但是因为太快了，其它两个线程，陆续执行 iadd命令，
     * 进行写入操作，这就造成了其他线程没有接受到主内存n的改变，从而覆盖了原来的值，出现写丢失，这样也就让最终的结果少于20000。
     */
    public static void atomicity() {
        visibility visibility = new visibility();

        // 多线程情况下对变量进行写操作
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    visibility.numberPlus();
                }
            }).start();
        }

        // 确保上面多线程全部运行完毕
        // 默认有主线程和GC线程
        while (Thread.activeCount() > 2) {
            Thread.yield();
        }

        // 验证结果
        // 如果volatile保证原子性，那么最终计算的结果应该是20000
        System.out.println(Thread.currentThread().getName() + "\t" + "finally the value : " + visibility.number);
        // main	finally the value : 19868
    }
}

/**
 * 指令重排
 * Happen-Before先行发生规则
 * 如果光靠synchronized和volatile来保证程序执行过程中的原子性, 有序性, 可见性, 那么代码将会变得异常繁琐.
 * JMM提供了Happen-Before规则来约束数据之间是否存在竞争, 线程环境是否安全, 具体如下
 * <p>
 * 顺序原则
 * 一个线程内保证语义的串行性; a = 1; b = a + 1;
 * <p>
 * volatile规则
 * volatile变量的写，先发生于读，这保证了volatile变量的可见性,
 * <p>
 * 锁规则
 * 解锁（unlock）必然发生在随后的加锁（lock）前.
 * <p>
 * 传递性
 * A先于B，B先于C，那么A必然先于C.
 * <p>
 * 线程启动, 中断, 终止
 * 线程的start()方法先于它的每一个动作.
 * 线程的中断（interrupt()）先于被中断线程的代码.
 * 线程的所有操作先于线程的终结（Thread.join()）.
 * <p>
 * 对象终结
 * 对象的构造函数执行结束先于finalize()方法
 */
class OrderRearrangement {
    int a = 0;
    boolean flag = false;

    public void m1() {
        a = 1; // 语句1
        flag = true; // 语句2
    }

    public void m2() {
        if (flag) {
            a = a + 5; // 语句3
        }
        // 可能是6或1或5或0
        System.out.println("value :" + a);
    }

    public static void rearrange() {
        OrderRearrangement orderRearrangement = new OrderRearrangement();
        new Thread(orderRearrangement::m2).start();
        new Thread(orderRearrangement::m1).start();
    }
}

/**
 * 懒汉式，双重检查 线程不安全的问题。使用volatile可以保证线程安全
 */
class Singleton {

    public Boolean flag;

    /**
     * 不加volatile，可能造成对象被赋予地址值之后还没来得及初始化；就被另外一个线程拿走使用了，在访问对象内部属性时会造成空指针异常
     *
     * new Singleton(); 可以分为三步完成
     * 1.memory = allocate(); 分配对象内存空间
     * 2.instance(memory); 初始化对象
     * 3.instance = memory; 设置instance指向刚分配的内存地址，此时instance != null
     *
     * 步骤2和步骤3不存在数据依赖关系，而且无论重排前还是重排后程序的执行结果在单线程中并没有改变，因此这种重排优化是允许的
     * 1.memory = allocate(); 分配对象内存空间
     * 2.instance = memory; 设置instance指向刚分配的内存地址，此时instance! =null，但是对象还没有初始化完成!
     * 3.instance(memory); 初始化对象
     *
     * 当一条线程访问instance不为null时，由于instance实例未必已初始化完成，也就造成了线程安全问题。
     */
    public volatile static Singleton SINGLETON;

    private Singleton() {
        this.flag = true;
    }

    public static Singleton getInstance() {
        if (SINGLETON == null) {
            synchronized (Singleton.class) {
                if (SINGLETON == null) {
                    SINGLETON = new Singleton();
                }
            }
        }
        return SINGLETON;
    }

    public static void m1() {

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                Singleton instance = getInstance();
                System.out.println(instance.flag.toString());
            }).start();
        }
    }
}
